package admin

import (
	"center/db"
	"common/kms"
	"context"
	"crypto/subtle"
	"errors"
	"fmt"
	"github.com/gorilla/mux"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
	"net"
	"net/http"
)

// Config defines configuration for debug server.
type Config struct {
	Address            string `help:"admin peer http listening address" releaseDefault:"0.0.0.0:10004" devDefault:"0.0.0.0:10004"`
	OpenPprof          bool   `help:"是否打开pprof" releaseDefault:"false" default:"true"`
	AuthorizationToken string `internal:"true"`
	AllowedOauthHost   string `help:"the oauth host allowed to bypass token authentication."`
}

// Server provides endpoints for administrative tasks.
type Server struct {
	log    *zap.Logger
	server http.Server
	db     *db.CenterDB
	config Config

	kmsClient *kms.Client
}

// NewServer returns a new administration Server.
func NewServer(log *zap.Logger, db *db.CenterDB, config Config, kmsClient *kms.Client) *Server {
	server := &Server{
		log:       log,
		db:        db,
		config:    config,
		kmsClient: kmsClient,
	}

	root := mux.NewRouter()

	if config.OpenPprof {
		registerPProf(root)
	}

	api := root.PathPrefix("/api/").Subrouter()
	//api.Use(allowedAuthorization(log, config))

	api.Use(server.allowedAuthorization(log, config))

	// When adding new options, also update README.md
	api.HandleFunc("/users", server.addUser).Methods("POST")
	//api.HandleFunc("/users/{useremail}", server.updateUser).Methods("PUT")
	api.HandleFunc("/users/{useremail}", server.userInfo).Methods("GET")
	//api.HandleFunc("/users/{useremail}", server.deleteUser).Methods("DELETE")
	api.HandleFunc("/buckets", server.addBucket).Methods("POST")
	//api.HandleFunc("/apikeys/{apikey}", server.deleteAPIKey).Methods("DELETE")

	server.server.Handler = root
	return server
}

// Run starts the admin endpoint.
func (server *Server) Run(ctx context.Context) error {
	listener, err := net.Listen("tcp", server.config.Address)
	if err != nil {
		return err
	}
	ctx, cancel := context.WithCancel(ctx)
	var group errgroup.Group
	group.Go(func() error {
		<-ctx.Done()
		return Error.Wrap(server.server.Shutdown(context.Background()))
	})
	group.Go(func() error {
		defer cancel()
		err := server.server.Serve(listener)
		if errors.Is(err, http.ErrServerClosed) {
			err = nil
		}
		return Error.Wrap(err)
	})
	server.log.Info("admin start: " + server.config.Address)
	return group.Wait()
}

// Close closes server and underlying listener.
func (server *Server) Close() error {
	return Error.Wrap(server.server.Close())
}

func (server *Server) allowedAuthorization(log *zap.Logger, config Config) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Host != config.AllowedOauthHost {
				// not behind the proxy; use old authentication method.
				if config.AuthorizationToken == "" {
					server.sendJSONError(w, "Authorization not enabled.",
						"", http.StatusForbidden)
					return
				}

				equality := subtle.ConstantTimeCompare(
					[]byte(r.Header.Get("Authorization")),
					[]byte(config.AuthorizationToken),
				)
				if equality != 1 {
					server.sendJSONError(w, "Forbidden",
						"", http.StatusForbidden)
					return
				}
			}

			server.log.Info(
				"admin action",
				zap.String("host", r.Host),
				zap.String("user", r.Header.Get("X-Forwarded-Email")),
				zap.String("action", fmt.Sprintf("%s-%s", r.Method, r.RequestURI)),
				zap.String("queries", r.URL.Query().Encode()),
			)

			r.Header.Set("Cache-Control", "must-revalidate")
			next.ServeHTTP(w, r)
		})
	}
}
